package orm

import (
	"errors"
	"fmt"
	"gitee.com/xlizy/common-go/base/enums/common_error"
	"gitee.com/xlizy/common-go/base/response"
	"gitee.com/xlizy/common-go/components/orm/tx"
	"gitee.com/xlizy/common-go/utils/zlog"
	sqlDriverMySql "github.com/go-sql-driver/mysql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"os"
	"strings"
	"time"
)

type RootConfig struct {
	Orm   ormConfig             `yaml:"orm"`
	Multi map[string]*ormConfig `yaml:"orm-multi"`
}

type ormConfig struct {
	Dsn             string        `yaml:"dsn"`
	MaxOpen         int           `yaml:"max-open"`
	MaxIdle         int           `yaml:"max-idle"`
	ConnMaxLifetime time.Duration `yaml:"conn-max-lifetime"`
}

var _primaryDb *gorm.DB
var _multiDb = make(map[string]*gorm.DB)

var wd = ""

type ormLoggerWriter struct {
	logger.Writer
}

var _log_traceStr_fmt = "%s\n[%.3fms] [rows:%v] %s"
var _log_traceErrStr_fmt = "%s %s\n[%.3fms] [rows:%v] %s"

func (w ormLoggerWriter) Printf(template string, args ...interface{}) {
	if len(args) > 0 {
		args[0] = strings.Replace(args[0].(string), wd+"/", "", 1)
	}
	sql := ""
	sqlTime := ""
	rows := ""
	dbMsg := ""
	caller := ""
	slowTime := 1500.0
	if template == _log_traceStr_fmt {
		sql = fmt.Sprintf("%s", args[3])
		sqlTime = fmt.Sprintf("%.3fms", args[1])
		rows = fmt.Sprintf("%v", args[2])
		caller = fmt.Sprintf("%s", args[0])
		if args[1].(float64) > slowTime {
			zlog.Info("sql:[{}],time:{},rows:{},caller:{},慢SQL", sql, sqlTime, rows, caller)
		} else {
			zlog.Info("sql:[{}],time:{},rows:{},caller:{}", sql, sqlTime, rows, caller)
		}
	} else if template == _log_traceErrStr_fmt {
		sql = fmt.Sprintf("%s", args[4])
		sqlTime = fmt.Sprintf("%.3fms", args[2])
		rows = fmt.Sprintf("%v", args[3])
		dbMsg = fmt.Sprintf("%s", args[1])
		err := args[1].(error)
		dbMsg = err.Error()
		caller = fmt.Sprintf("%s", args[0])
		if args[2].(float64) > slowTime {
			zlog.Info("sql:[{}],time:{},rows:{},dbMsg:{},caller:{},慢SQL", sql, sqlTime, rows, dbMsg, caller)
		} else {
			zlog.Info("sql:[{}],time:{},rows:{},dbMsg:{},caller:{}", sql, sqlTime, rows, dbMsg, caller)
		}
	} else {
		template = strings.Replace(template, "\r", " ", -1)
		zlog.Info(template, args...)
	}
}

func NewConfig() *RootConfig {
	return &RootConfig{}
}

// InitOrm 初始化数据源
func InitOrm(rc *RootConfig) {
	wd, _ = os.Getwd()
	newLogger := logger.New(
		&ormLoggerWriter{},
		logger.Config{
			SlowThreshold:             1 * time.Minute,
			LogLevel:                  logger.Silent,
			IgnoreRecordNotFoundError: true,
			Colorful:                  false,
		},
	)
	if rc.Multi != nil && len(rc.Multi) > 0 {
		for key, _config := range rc.Multi {
			var err error
			_db, err := gorm.Open(mysql.Open(_config.Dsn), &gorm.Config{PrepareStmt: true, Logger: newLogger})
			if err != nil {
				zlog.Error("连接Mysql异常:{}", err)
				panic(err)
			}
			if _db.Error != nil {
				zlog.Error("连接Mysql异常:{}", _db.Error.Error())
				panic(_db.Error)
			}
			sqlDB, _ := _db.DB()
			sqlDB.SetMaxOpenConns(_config.MaxOpen)
			sqlDB.SetMaxIdleConns(_config.MaxIdle)
			sqlDB.SetConnMaxLifetime(_config.ConnMaxLifetime)
			if _primaryDb == nil {
				_primaryDb = _db
			}
			_multiDb[key] = _db
		}
	} else {
		var err error
		db, err := gorm.Open(mysql.Open(rc.Orm.Dsn), &gorm.Config{PrepareStmt: true, Logger: newLogger})
		if err != nil {
			zlog.Error("连接Mysql异常:{}", err)
			panic(err)
		}
		if db.Error != nil {
			zlog.Error("连接Mysql异常:{}", db.Error.Error())
			panic(db.Error)
		}
		sqlDB, _ := db.DB()
		sqlDB.SetMaxOpenConns(rc.Orm.MaxOpen)
		sqlDB.SetMaxIdleConns(rc.Orm.MaxIdle)
		sqlDB.SetConnMaxLifetime(rc.Orm.ConnMaxLifetime)
		_primaryDb = db
	}

}

func Conn() *gorm.DB {
	var db *gorm.DB
	if tx.IsInTX() {
		if tx.GetDB() != nil {
			return tx.GetDB()
		} else {
			db = _primaryDb.Begin()
			tx.SetDB(db)
			return db
		}
	}
	db = _primaryDb
	return db
}

func ConnMulti(key string) *gorm.DB {
	var db *gorm.DB
	if tx.IsInTX() {
		if tx.GetDB() != nil {
			return tx.GetDB()
		} else {
			db = _multiDb[key].Begin()
			tx.SetDB(db)
			return db
		}
	}
	db = _multiDb[key]
	return db
}

func ErrHandler(err error) *response.Response {
	if errors.Is(err, gorm.ErrRecordNotFound) {

	} else {
		tx.NeedRollback()
		zlog.Error("sql exec error:{}", err.Error())
		if e, ok := err.(*sqlDriverMySql.MySQLError); ok {
			ce := common_error.SystemError
			if e.Number == uint16(1008) {
				ce = common_error.MysqlErr1008
			} else if e.Number == uint16(1012) {
				ce = common_error.MysqlErr1012
			} else if e.Number == uint16(1020) {
				ce = common_error.MysqlErr1020
			} else if e.Number == uint16(1021) {
				ce = common_error.MysqlErr1021
			} else if e.Number == uint16(1022) {
				ce = common_error.MysqlErr1022
			} else if e.Number == uint16(1037) {
				ce = common_error.MysqlErr1037
			} else if e.Number == uint16(1044) {
				ce = common_error.MysqlErr1044
			} else if e.Number == uint16(1045) {
				ce = common_error.MysqlErr1045
			} else if e.Number == uint16(1048) {
				ce = common_error.MysqlErr1048
			} else if e.Number == uint16(1049) {
				ce = common_error.MysqlErr1049
			} else if e.Number == uint16(1054) {
				ce = common_error.MysqlErr1054
			} else if e.Number == uint16(1062) {
				ce = common_error.MysqlErr1062
			} else if e.Number == uint16(1065) {
				ce = common_error.MysqlErr1065
			} else if e.Number == uint16(1114) {
				ce = common_error.MysqlErr1114
			} else if e.Number == uint16(1130) {
				ce = common_error.MysqlErr1130
			} else if e.Number == uint16(1133) {
				ce = common_error.MysqlErr1133
			} else if e.Number == uint16(1141) {
				ce = common_error.MysqlErr1141
			} else if e.Number == uint16(1142) {
				ce = common_error.MysqlErr1142
			} else if e.Number == uint16(1143) {
				ce = common_error.MysqlErr1143
			} else if e.Number == uint16(1149) {
				ce = common_error.MysqlErr1149
			} else if e.Number == uint16(1169) {
				ce = common_error.MysqlErr1169
			} else if e.Number == uint16(1216) {
				ce = common_error.MysqlErr1216
			}
			return response.Error(ce, e.Message)
		} else if err.Error() == "WHERE conditions required" {
			return response.Error(common_error.MysqlErr, nil)
		}
	}
	return response.Succ()
}
